<?php
class Bliss_DataReport
{
	/**
	 * The fields allowed to be filtered.
	 *
	 * @var	array
	 */
	protected $_filters;

	/**
	 * The report's column information
	 *
	 * @var array
	 */
	protected $_columns;

	/**
	 * Whether to display column labels
	 *
	 * @var bool
	 */
	protected $_displayLabels = true;
	
	/**
	 * Holds the database instance
	 *
	 * @var	Zend_Db
	 */
	protected $_db;
	
	/**
	 * Holds the report's query object
	 *
	 * @var	Zend_Db_Select
	 */
	protected $_query;

	/**
	 * Holds the default parameters used if no custom parameters exist
	 *
	 * @var	Zend_Config
	 */
	protected $_defaultParams;

	/**
	 * Holds the custom parameters used to alter the query
	 *
	 * @var Zend_Config
	 */
	protected $_customParams;

	/**
	 * Function to map the results with.
	 * 
	 * @var mixed
	 */
	protected $_mapFunction;

	/**
	 * Whether to use a paginator
	 *
	 * @var bool
	 */
	protected $_usePaginator = false;

	/**
	 * Holds the current page number
	 * Used only by the pagination class
	 *
	 * @var int
	 */
	protected $_pageNumber = 1;

	/**
	 * Holds the amount of items to display on the pagination control
	 *
	 * @var int
	 */
	protected $_pageRange	= 10;

	/**
	 * Specifies how many items per page there should be
	 * Used only by the pagination class
	 * 
	 * @var int
	 */
	protected $_perPage;

	/**
	 * Stores the report's results
	 *
	 * @var mixed
	 */
	protected $_results;
	
	/**
	 * Class constructor
	 *
	 * @param	Zend_Db
	 */
	public function __construct($db, $params=null)
	{
		$this->_db	= $db;

		if(isset($params)) {
			$this->setParameters($params);
		}
	}

	/**
	 * Sets the default parameters
	 *
	 * @param mixed
	 * @see	$_defaultParams
	 */
	public function setDefaultParameters($params)
	{
		if(!$params instanceof Zend_Config) {
			$params	= new Zend_Config($params);
		}
		$this->_defaultParams	= $params;
	}
	
	/**
	 * Sets the custom parameters
	 *
	 * @param	Zend_Config
	 * @see	$_customParams
	 */
	public function setParameters($params) 
	{
		if(!$params instanceof Zend_Config) {
			$params	= new Zend_Config($params);
		}
		$this->_customParams	= $params;
	}

	/**
	 * Collects the default and custom parameters
	 *
	 * @return array
	 */
	public function getParameters()
	{
		$default	= isset($this->_defaultParams) ? $this->_defaultParams->toArray() : array();
		$custom		= isset($this->_customParams) ? $this->_customParams->toArray() : array();

		return array_merge($default, $custom);
	}

	/**
	 * Returns the specified param group
	 *
	 * @param string $key
	 * @return Zend_Config
	 */
	public function getParam($key)
	{
		$results = array();
		if(isset($this->_defaultParams->{$key})) {
			$results = array_merge($results, $this->_defaultParams->{$key}->toArray());
		}

		if(isset($this->_customParams->{$key})) {
			$results = array_merge($results, $this->_customParams->{$key}->toArray());
		}

		return new Zend_Config($results);
	}

	/**
	 * Checks if the report is sortable.
	 *
	 * @return bool
	 */
	public function isSortable()
	{
		$options = $this->getParam('options');
		return isset($options->sortable) && $options->sortable == false ? false : true;
	}
	
	/**
	 * Retrieves the Zend_Db_Select object
	 *
	 * @return	Zend_Db_Select
	 */
	public function getDbSelect() 
	{
		return $this->_db->select();
	}

	/**
	 * Sets the base query for the report
	 *
	 * @param	Zend_Db_Select
	 */
	public function setBaseQuery(Zend_Db_Select $query)
	{
		$this->_query	= $query;
	}

	/**
	 * Sets the fields that can be filtered
	 * 
	 * @param array $filters Collection of Bliss_DataReport_Filter objects
	 */
	public function setFilters(array $filters)
	{
		$this->_filters	= $filters;
	}

	/**
	 * Gets the form's filters
	 *
	 * @return array
	 */
	public function getFilters()
	{
		return $this->_filters;
	}

	/**
	 * Set a function to map to the results
	 * 
	 * @param mixed $func
	 */
	public function setResultMap($func)
	{
		$this->_mapFunction	= $func;
	}

	/**
	 * Sets the available display columns
	 *
	 * @param array $cols
	 */
	public function setColumns(array $cols)
	{
		$filters	= array();
		$columns	= array();
		foreach($cols as $name => $item) {
			if(isset($item['filter'])) {
				$filters[$name]	= new Bliss_DataReport_Filter($item['field'], $item['label'], $item['filter']);
			}
			$columns[$name]	= $item;
		}

		$this->_columns	= $columns;
		$this->setFilters($filters);
	}

	/**
	 * Returns the column information
	 *
	 * @return array
	 */
	public function getColumns()
	{
		$this->getResults();
		
		return $this->_columns;
	}

	/**
	 * Returns the visible columns
	 * 
	 * @return array
	 */
	public function getVisibleColumns()
	{
		$visible = array();
		foreach($this->getColumns() as $key => $column) {
			if(isset($column['visible']) && $column['visible'] == false) continue;
			$visible[$key] = $column;
		}
		return $visible;
	}

	/**
	 * Gets the column labels
	 *
	 * @return array
	 */
	public function getColumnLabels()
	{
		$columns	= array();
		foreach($this->getVisibleColumns() as $key => $column) {
			$columns[$key]	= $column;
		}
		return $columns;
	}

	/**
	 * Retuns the number of visible columns
	 * 
	 * @return int
	 */
	public function getColumnCount()
	{
		$visible = $this->getVisibleColumns();
		return count($visible);
	}

	/**
	 * Sets whether to display column labels
	 * 
	 * @param bool $bool
	 */
	public function setDisplayLabels($bool=true)
	{
		$this->_displayLabels	= $bool;
	}

	/**
	 * Returns if the report should show column labels
	 *
	 * @return bool
	 */
	public function displayLabels()
	{
		return $this->_displayLabels;
	}

	/**
	 * Sets whether to use a paginator for the results.
	 * 
	 * @param bool $bool
	 */
	public function usePaginator($bool=true)
	{
		$this->_usePaginator	= $bool;
	}

	/**
	 * Sets the current page number
	 *
	 * @param int $page
	 */
	public function setPageNumber($page)
	{
		$this->_pageNumber = $page;
	}

	/**
	 * Sets the items to show per page
	 * 
	 * @param int $i
	 */
	public function setItemsPerPage($i)
	{
		$this->_perPage = $i;
	}

	/**
	 * Sets the number of pages to display in the pagination controls
	 *
	 * @param int $i
	 */
	public function setPageRange($i)
	{
		$this->_pageRange	= $i;
	}

	/**
	 * Checks if the report has results
	 *
	 * @return bool
	 */
	public function hasResults()
	{
		$this->getResults();
		
		return isset($this->_results) && count($this->_results);
	}

	/**
	 * Applies any parameters to the query and fetches the results.
	 *
	 * @return mixed This may return a standard array or a Zend_Paginator object
	 */
	public function getResults()
	{
		if(isset($this->_results)) {
			return $this->_results;
		}

		if(!isset($this->_query)) {
			throw new Bliss_DataReport_Exception("No base query has been specified for the data report.");
		}

		$options	= $this->getParam('options');
		if($options->results && $options->results != 'all') {
			$this->usePaginator(true);
			$this->setItemsPerPage($options->results);
		}

		$this->_applySorts();
		$this->_applyFilters();
		$this->_applyAlterations();

		if($this->_usePaginator) {
			$adapter	= new Zend_Paginator_Adapter_DbSelect($this->_query);
			$this->_results	= new Zend_Paginator($adapter);
			$this->_results->setCurrentPageNumber($this->_pageNumber);
			$this->_results->setPageRange($this->_pageRange);
			if(isset($this->_perPage)) {
				$this->_results->setItemCountPerPage($this->_perPage);
			}
		} else {
			$this->_results	= $this->_db->fetchAll($this->_query);
		}
		
		return $this->_results;
	}

	/**
	 * Wraps the result rows and items in Bliss_DataReport_Result_Row and
	 * Bliss_DataReport_Result_Item objects.
	 *
	 * @return array
	 */
	public function getResultSet()
	{
		$results	= array();
		foreach($this->getResults() as $result) {
			$result		= $this->_filterResultRow($result);
			$results[]	= new Bliss_DataReport_Result_Row($result);
		}

		if(isset($this->_mapFunction)) {
			$results = array_map($this->_mapFunction, $results);
		}
		return $results;
	}

	/**
	 * Filters out only the columns that should be displayed
	 *
	 * @param array $result
	 * @return array
	 */
	protected function _filterResultRow($result)
	{
		$columns = $this->getColumns();
		foreach($columns as $key => $column) {
			$columns[$key]['value']		= isset($result[$key]) ? $result[$key] : null;
			$columns[$key]['visible']	= isset($column['visible']) ? $column['visible'] : true;
		}
		return $columns;
	}

	/**
	 * Returns a standard array of the report results.
	 * 
	 * @return array
	 */
	public function getResultArray()
	{
		$results	= array();
		foreach($this->_results as $result) {
			$results[]	= $result;
		}
		return $results;
	}

	/**
	 * Applies any custom sorting to the query
	 *
	 * @return void
	 */
	protected function _applySorts()
	{
		if(count($this->_customParams->sort)) {
			$sorts	= $this->_customParams->sort;
		} else if(count($this->_defaultParams->sort)) {
			$sorts	= $this->_defaultParams->sort;
		} else {
			return;
		}

		foreach($sorts as $field => $direction) {
			if(isset($this->_columns[$field])) {
				$fieldName	= isset($this->_columns[$field]['sortField']) ? $this->_columns[$field]['sortField'] : $this->_columns[$field]['field'];
				$this->_query->order("{$fieldName} {$direction}");
				$this->_columns[$field]['sorting']	= $direction;
			}
		}
	}

	/**
	 * Applies any custom filters to the query
	 *
	 * @return void
	 */
	protected function _applyFilters()
	{
		if(count($this->_customParams->filter)) {
			$filters	= $this->_customParams->filter;
		} else if(count($this->_defaultParams->filter)) {
			$filters	= $this->_defaultParams->filter;
		} else {
			return;
		}

		foreach($filters as $name => $value) {
			if(isset($this->_filters[$name])) {
				$f	= $this->_filters[$name];
				$f->setValue($value);

				if(is_string($value) && strlen(trim($value))) {
					$this->_query->where($f->getCondition(), $f->getValue());

				} else if($value instanceOf Zend_Config) {
					$pairs = array();
					foreach($value as $i => $v) {
						$pairs[] = str_replace('?', "'".$f->formatValue($v)."'", $f->getCondition());
					}
					$this->_query->where(implode(' OR ', $pairs));
				}
			}
		}
	}

	/**
	 * Alters the columns that can be displayed
	 * 
	 * @return void
	 */
	protected function _applyAlterations()
	{
		if(!count($this->_customParams->alter)) {
			return;
		}

		foreach($this->_customParams->alter as $name => $value) {
			if(isset($this->_columns[$name])) {
				$this->_columns[$name]['visible'] = $value;
			}
		}
	}

	/**
	 * Returns the form for the report
	 *
	 * @return Zend_Form
	 */
	public function getForm()
	{
		$form	= new Bliss_DataReport_Form($this);
		$form->addReportForm('filter');
		$form->addReportForm('alter');

		return $form;
	}
}
?>